﻿using System;
using System.Reflection;
using System.Text;
using msilgc.common;
using msilgc.common.Attributes;

namespace msilgc.runtime {
    internal class MarshallingCodeGenerator {
        
        public string WriteCode(MethodInfo method, out string className, bool debug) {
            StringBuilder code = new StringBuilder(10240);
            string driverMethodName = string.Concat(method.Name, "_driver");
            className = driverMethodName + "Proxy";
            WriteUsings(code);
            code.AppendLine("namespace msilgc.runtime.driver {");
            code.AppendLine("\tpublic sealed class " + className + " : msilgc.common.BaseMethodHandle {");
            DelegateWriter.WriteDelegateDeclaration(code, method, driverMethodName, true);
            WriteConstructor(code, className);
            WriteCompileMethod(code, driverMethodName, debug);
            DelegateWriter.WriteInvokeMethod(code, method);
            WriteDeviceMethod(code);
            code.AppendLine("\t}");
            code.AppendLine("}");
            className = "msilgc.runtime.driver." + className;
            return code.ToString();
        }
        
        private static void WriteConstructor(StringBuilder code, string className)
        {
            code.AppendLine("\t\t\tprivate readonly MethodInfo _method;");
            code.AppendLine("\t\t\tpublic " + className + "(MethodInfo method) {");
            code.AppendLine("\t\t\t\t_method = method;");
            code.AppendLine("\t\t\t}");
        }
        
        private static void WriteCompileMethod(StringBuilder code, string driverMethodName, bool debug)
        {
            string debugStr = debug ? "true" : "false";
            string delegateName = driverMethodName + "Delegate";
            code.AppendLine("\t\t\tpublic override void Compile() {");
            code.AppendLine("\t\t\t\tDirectoryInfo tempFolder = NativeMethodHelpers.CreateTemporaryFolder();");
            code.AppendLine("\t\t\t\tstring messages = \"\", code = \"\";");
            code.AppendLine("\t\t\t\ttry { new CudaCompilerHelper().CreateCudaImplementation(tempFolder, _method, out messages, out code, " + debugStr + "); } ");
            code.AppendLine("\t\t\t\tfinally { CompilerMessages += messages; GeneratedKernelCode += code; } ");
            code.AppendLine("\t\t\t\t_methodDelegate = (" + delegateName +
                            ")NativeMethodHelperFactory.Create().GetMethodHandle(Path.Combine(tempFolder.FullName, \"driver.dll\"), \"" +
                            driverMethodName + "\", typeof(" + delegateName + "));");
            code.AppendLine("\t\t\t\t_devId = DeviceID;");
            code.AppendLine("\t\t\t}");
        }

        private static void WriteDeviceMethod(StringBuilder code)
        {
            code.AppendLine("\t\t\tprivate int _devId = (-1);");
            code.AppendLine("\t\t\tprivate int DeviceID { get { if(_devId == (-1)) { _devId = CudaNativeHelper.GetFastestCudaDeviceId(); } return _devId; } }");
        }

        private static void WriteUsings(StringBuilder code)
        {
            code.AppendLine("using System;");
            code.AppendLine("using System.IO;");
            code.AppendLine("using System.Reflection;");
            code.AppendLine("using msilgc.common;");
            code.AppendLine("using msilgc.common.Types;");
            code.AppendLine("using msilgc.driver.cuda;");
            code.AppendLine();
        }
    }

    internal static class DelegateWriter {
        public static void WriteInvokeMethod(StringBuilder code, MethodInfo method) {
            code.AppendLine("\t\t\tprotected override void Invoke(Grain threadDim, Grain blockDim, params object[] args) {");
            code.AppendLine("\t\t\t\tif(_methodDelegate == null) { Compile(); }");
            //code.AppendLine("\t\t\t\tbase.StartTimer();");
            int i = (-1);
            foreach (ParameterInfo parameter in method.GetParameters()) {
                i++;
                if (!IsGrainParameter(parameter)) {
                    code.Append("\t\t\t\t");
                    if (_IsPrimitive(parameter.ParameterType)) {
                        code.Append(GetMarshalledTypeString(parameter.ParameterType));
                    } else {
                        code.Append("IPinnedMemoryResource");
                    }
                    code.Append(" arg_");
                    code.Append(i.ToString());
                    code.Append(" = ");
                    if (!_IsPrimitive(parameter.ParameterType)) {
                        code.Append("new PinnedMemoryResource<");
                        code.Append(GetUnMarshalledTypeString(parameter.ParameterType).Replace("[]", ""));
                        code.Append(">(");
                    }

                    code.Append("(");
                    code.Append(GetUnMarshalledTypeString(parameter.ParameterType));
                    code.Append(")");
                    code.Append("args[");
                    code.Append(i.ToString());
                    code.Append("]");
                    if (!_IsPrimitive(parameter.ParameterType)) {
                        code.Append(")");
                    }
                    code.AppendLine(";");
                }
            }

            code.AppendLine("\t\t\t\ttry {");
            code.AppendLine("\t\t\t\t\tbase.StartTimer();");
            code.Append("\t\t\t\t\t_methodDelegate(");
            i = (-1);
            foreach (ParameterInfo parameter in method.GetParameters()) {
                i++;
                if (IsGrainParameter(parameter)) {
                } else if (_IsPrimitive(parameter.ParameterType)) {
                    code.Append("arg_" + i + ",");
                } else {
                    code.Append("arg_" + i + ".Data,");
                }
            }
            code.AppendLine("threadDim, blockDim, DeviceID);");
            code.AppendLine("\t\t\t\t\tNativeMethodHelperFactory.Create().ThrowForWin32Error();");
            code.AppendLine("\t\t\t\t\tbase.StopTimer();");
            code.AppendLine("\t\t\t\t} finally { ");
            i = (-1);
            foreach (ParameterInfo parameter in method.GetParameters()) {
                i++;
                if (!IsGrainParameter(parameter) && !_IsPrimitive(parameter.ParameterType)) {
                    code.AppendLine("\t\t\t\t\targ_" + i + ".Dispose();");
                }
            }
            code.AppendLine("\t\t\t\t}");
            code.AppendLine("\t\t\t}");
        }

        public static bool _IsPrimitive(Type type) {
            if (type == typeof(int) || type == typeof(Int32)) {
                return true;
            } else if (type == typeof(uint) || type == typeof(UInt32)) {
                return true;
            } else if (type == typeof(short) || type == typeof(Int16)) {
                return true;
            } else if (type == typeof(ushort) || type == typeof(UInt16)) {
                return true;
            } else if (type == typeof(long) || type == typeof(Int64)) {
                return true;
            } else if (type == typeof(ulong) || type == typeof(UInt64)) {
                return true;
            } else if (type == typeof(float) || type == typeof(Single)) {
                return true;
            } else if (type == typeof(double) || type == typeof(Double)) {
                return true;
            } else if (type == typeof(char) || type == typeof(Char)) {
                return true;
            } else if (type == typeof(int[]) || type == typeof(Int32[])) {
                return false;
            } else if (type == typeof(uint[]) || type == typeof(UInt32[])) {
                return false;
            } else if (type == typeof(short[]) || type == typeof(Int16[])) {
                return false;
            } else if (type == typeof(ushort[]) || type == typeof(UInt16[])) {
                return false;
            } else if (type == typeof(long[]) || type == typeof(Int64[])) {
                return false;
            } else if (type == typeof(ulong[]) || type == typeof(UInt64[])) {
                return false;
            } else if (type == typeof(float[]) || type == typeof(Single[])) {
                return false;
            } else if (type == typeof(double[]) || type == typeof(Double[])) {
                return false;
            } else if (type == typeof(char[]) || type == typeof(Char[]) || type == typeof(byte[]) || type == typeof(Byte[])) {
                return false;
            } else if (type == typeof(void)) {
                return true;
            } else if (type == typeof(bool) || type == typeof(Boolean)) {
                return true;
            } else {
                throw new Exception("unexpected type: " + type);
            }
        }

        public static void WriteDelegateDeclaration(StringBuilder code, MethodInfo method, string driverMethodName, bool declareField) {
            code.Append("\t\t\tprivate delegate ");
            code.Append(GetMarshalledTypeString(method.ReturnType));
            code.Append(" ");
            code.Append(driverMethodName);
            code.Append("Delegate(");
            int i = (-1);
            foreach (ParameterInfo parameter in method.GetParameters()) {
                if (!IsGrainParameter(parameter)) {
                    i++;
                    code.Append(GetMarshalledTypeString(parameter.ParameterType));
                    code.Append(" arg_");
                    code.Append(i.ToString());
                    code.Append(", ");
                }
            }
            code.AppendLine("Grain threadGrain, Grain dataGrain, int devId);");
            if (declareField) {
                code.Append("\t\t\tprivate ");
                code.Append(driverMethodName);
                code.AppendLine("Delegate _methodDelegate;");
            }
        }

        public static string GetUnMarshalledTypeString(Type type) {
            string typeName;
            if (type == typeof(int) || type == typeof(Int32)) {
                typeName = "int";
            } else if (type == typeof(uint) || type == typeof(UInt32)) {
                typeName = "uint";
            } else if (type == typeof(short) || type == typeof(Int16)) {
                typeName = "short";
            } else if (type == typeof(ushort) || type == typeof(UInt16)) {
                typeName = "ushort";
            } else if (type == typeof(long) || type == typeof(Int64)) {
                typeName = "long";
            } else if (type == typeof(ulong) || type == typeof(UInt64)) {
                typeName = "ulong";
            } else if (type == typeof(float) || type == typeof(Single)) {
                typeName = "float";
            } else if (type == typeof(double) || type == typeof(Double)) {
                typeName = "double";
            } else if (type == typeof(char) || type == typeof(Char)) {
                typeName = "char";
            } else if (type == typeof(int[]) || type == typeof(Int32[])) {
                typeName = "int[]";
            } else if (type == typeof(uint[]) || type == typeof(UInt32[])) {
                typeName = "uint[]";
            } else if (type == typeof(short[]) || type == typeof(Int16[])) {
                typeName = "short[]";
            } else if (type == typeof(ushort[]) || type == typeof(UInt16[])) {
                typeName = "ushort[]";
            } else if (type == typeof(long[]) || type == typeof(Int64[])) {
                typeName = "long[]";
            } else if (type == typeof(ulong[]) || type == typeof(UInt64[])) {
                typeName = "ulong[]";
            } else if (type == typeof(float[]) || type == typeof(Single[])) {
                typeName = "float[]";
            } else if (type == typeof(double[]) || type == typeof(Double[])) {
                typeName = "double[]";
            } else if (type == typeof(char[]) || type == typeof(Char[])) {
                typeName = "char[]";
            } else if (type == typeof(byte[]) || type == typeof(Byte[])) {
                typeName = "byte[]";
            } else if (type == typeof(void)) {
                typeName = "void";
            } else if (type == typeof(bool) || type == typeof(Boolean)) {
                typeName = "bool";
            } else {
                throw new Exception("unexpected type: " + type);
            }
            return typeName;
        }

        public static string GetMarshalledTypeString(Type type) {
            string typeName;
            if (type == typeof(int) || type == typeof(Int32)) {
                typeName = "int";
            } else if (type == typeof(uint) || type == typeof(UInt32)) {
                typeName = "uint";
            } else if (type == typeof(short) || type == typeof(Int16)) {
                typeName = "short";
            } else if (type == typeof(ushort) || type == typeof(UInt16)) {
                typeName = "ushort";
            } else if (type == typeof(long) || type == typeof(Int64)) {
                typeName = "long";
            } else if (type == typeof(ulong) || type == typeof(UInt64)) {
                typeName = "ulong";
            } else if (type == typeof(float) || type == typeof(Single)) {
                typeName = "float";
            } else if (type == typeof(double) || type == typeof(Double)) {
                typeName = "double";
            } else if (type == typeof(char) || type == typeof(Char) || type == typeof(byte) || type == typeof(Byte)) {
                typeName = "char";
            } else if (type == typeof(int[]) || type == typeof(Int32[])) {
                typeName = "IntArray";
            } else if (type == typeof(uint[]) || type == typeof(UInt32[])) {
                typeName = "IntArray";
            } else if (type == typeof(short[]) || type == typeof(Int16[])) {
                typeName = "IntArray";
            } else if (type == typeof(ushort[]) || type == typeof(UInt16[])) {
                typeName = "IntArray";
            } else if (type == typeof(long[]) || type == typeof(Int64[])) {
                typeName = "IntArray";
            } else if (type == typeof(ulong[]) || type == typeof(UInt64[])) {
                typeName = "IntArray";
            } else if (type == typeof(float[]) || type == typeof(Single[])) {
                typeName = "IntArray";
            } else if (type == typeof(double[]) || type == typeof(Double[])) {
                typeName = "IntArray";
            } else if (type == typeof(char[]) || type == typeof(Char[]) || type == typeof(byte[]) || type == typeof(Byte[])) {
                typeName = "IntArray";
            } else if (type == typeof(void)) {
                typeName = "void";
            } else if (type == typeof(bool) || type == typeof(Boolean)) {
                typeName = "int";
            } else {
                throw new Exception("unexpected type: " + type);
            }
            return typeName;
        }

        public static bool IsGrainParameter(ParameterInfo parameter) {
            return
            parameter.ParameterType == typeof(Grain) && (
                parameter.GetCustomAttributes(typeof(ThreadIdxParameter), true).Length > 0 ||
                parameter.GetCustomAttributes(typeof(BlockIdxParameter), true).Length > 0 ||
                parameter.GetCustomAttributes(typeof(BlockDimParameter), true).Length > 0);
        }
    }
}
